﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Microsoft.Pex.Framework;

namespace AutomaTones.Model.Automaton_Model {
    /// <summary>
    ///   A cellular automaton consisting of an N-dimensional grid of cells.
    ///   Every new generation each cell gets a given state defined by one or more rules.
    ///   These rules consider a predefined set of neighbouring cells and hence decide a cells new state.
    ///   States are always represented using integers (discrete values).
    ///   States can be set and read via an indexer, eg. ca[0, 1] = 1;<br></br><br></br>
    ///   NOTE: Ideally this would be implemented using generics so states could also be decimals.
    ///   But due to C#'s lack of type constraint options on value types - this was omitted.<br></br><br></br>
    ///   This implementation is designed to allow a high degree of flexibility in the use of cellular automata.
    ///   Its N-dimensional and each cell may have its own state space and rule(s). Rules can use different
    ///   neighbourhoods which they process with their RuleDelegate - which again may use other delegates.
    ///   Thus this flexibility may make the overall semantics a little confusing at first.
    ///   However, one can setup a simple CA where all cells use the same space, rule and neighbourhood 
    ///   by simply using one of the constructors.
    /// </summary>
    /// <author>Anders Øland / anoe@itu.dk</author>
    [PexClass]
    [Serializable]
    public class CellularAutomaton : ICloneable {
        public uint Dimensions { get; private set; }
        public uint DimensionSize { get; private set; }
        public uint Generation { get; private set; }

        // _grid hold the cells of CA's the N-dimensional grid
        protected Cell[] Grid;

        public Rule DefaultRule { get; private set; }

        /// <summary>
        ///   Construct a new Cellular Automaton
        /// </summary>
        /// <param name = "dimensions">Number of dimensions</param>
        /// <param name = "dimensionSize">Size of the dimensions</param>
        /// <param name = "defaultRule">The default rule for all cells</param>
        /// <param name = "stateMin">default = 0</param>
        /// <param name = "stateMax">default = 1</param>
        /// <param name = "stateInit"></param>
        public CellularAutomaton(uint dimensions, uint dimensionSize, Rule defaultRule, int stateMin = 0,
                                 int stateMax = 1, int stateInit = 0) {
            Contract.Requires(0 < dimensions);
            Contract.Requires(0 < dimensionSize);
            Contract.Requires(null != defaultRule);
            Contract.Requires(dimensions == defaultRule.Dimensions);
            Contract.Requires(stateMin < stateMax);
            Contract.Requires(stateMin <= stateInit && stateMax >= stateInit);
            Init(dimensions, dimensionSize, defaultRule, stateMin, stateMax, stateInit);
        }

        private void Init(uint dimensions, uint dimensionSize, Rule defaultRule,
                          int stateMin = 0, int stateMax = 1, int stateInit = 0) {
            Contract.Requires(0 < dimensions && 0 < dimensionSize);
            Contract.Requires(dimensions == defaultRule.Dimensions);
            Contract.Requires(stateMin < stateMax);
            Contract.Requires(stateMin <= stateInit && stateMax >= stateInit);
            Generation = 0;
            Dimensions = dimensions;
            DimensionSize = dimensionSize;
            Grid = new Cell[(int)Math.Pow(DimensionSize, Dimensions)];
            for (int i = 0; i < Grid.Length; i++) {
                Grid[i] = new Cell(defaultRule, stateMin, stateMax, stateInit);
            }
            DefaultRule = defaultRule;
        }

        /// <summary>
        ///   Finds the right cell in the N-dimensional grid in the one dimensional array holding the cells
        /// </summary>
        [PexMethod]
        [Pure] // Tagged as pure since its being called from other contracts which I dont want to loosen 
        protected internal virtual Cell GetCell(params int[] coord) {
            Contract.Requires(null != coord);
            Contract.Requires(coord.Length == Dimensions);
            Contract.Ensures(Contract.Result<Cell>() != null);
            uint index = GetIndex(coord);
            return Grid[index];
        }

        /// <summary>
        ///   Translates a coordinate into an index number pointing to the corresponing location in the grid array.
        ///   Coordinates exceeding the bounds of the grid are 'wrapped around', 
        ///   thus giving the grid a so-called torus shape.
        /// </summary>
        [PexMethod]
        [Pure]
        protected uint GetIndex(int[] coord) {
            Contract.Requires(null != coord);
            Contract.Requires(0 < DimensionSize && 0 < Dimensions);
            Contract.Requires(coord.Length == Dimensions);
            Contract.Ensures((uint)Math.Pow(DimensionSize, Dimensions) > Contract.Result<uint>());
            var index = 0;
            for (int i = 1; i <= Dimensions; i++) {
                // Loop invariant
                Contract.Assert(0 <= index && Grid.Length > index);
                // Make sure coordinates which exceed the bounds of any dimension are 'wrapped around'
                int c = coord[i - 1] % (int)DimensionSize;
                c = 0 <= c ? c : c + (int)DimensionSize;
                // Sum the the effect of each coordinate to find the correct position in the grid array
                index += c * (int)Math.Pow(DimensionSize, Dimensions - i);
            }
            return (uint)index;
        }

        /// <summary>
        ///   Translates an index number into a coordinate.
        /// </summary>
        /// <param name = "index"></param>
        /// <returns></returns>
        private int[] GetCoordinate(uint index) {
            Contract.Requires(index < Grid.Length);
            Contract.Ensures(Dimensions == Contract.Result<int[]>().Length);
            // As long as the grid is rectangular (not jagged) this is equivalent to converting
            // index from base 10 to base DimensionSize.
            return Tools.Math.DecimalToBase(index, DimensionSize, Dimensions).Reverse().ToArray();
        }

        /// <summary>
        ///   Returns the State Object of the cell.
        /// </summary>
        /// <param name = "coord"></param>
        /// <returns>The state of the cell with the given coordinates</returns>
        public State GetCellState(params int[] coord) {
            Contract.Requires(coord.Length == Dimensions);
            Contract.Ensures(Contract.Result<State>() != null);
            return GetCell(coord).State;
        }

        /// <summary>
        ///   Returns the rule of the cell with the given coordinates.
        ///   Note that one rule may consist of many rules.
        /// </summary>
        /// <param name = "coord">Coordinates</param>
        /// <returns>IRule</returns>
        [PexMethod]
        public Rule GetCellRule(params int[] coord) {
            Contract.Requires(coord.Length == Dimensions);
            Contract.Ensures(Contract.Result<Rule>() != null);
            return GetCell(coord).Rule;
        }

        /// <summary>
        ///   Sets the rule of the cell with the given coordinates.
        /// </summary>
        /// <param name = "rule"></param>
        /// <param name = "coord">Coordinates</param>
        public void SetCellRule(Rule rule, params int[] coord) {
            Contract.Requires(coord.Length == Dimensions);
            Contract.Requires(rule != null);
            GetCell(coord).Rule = rule;
        }

        /// <summary>
        ///   Evolve this Cellular Automaton - move one generation forward
        ///   by applying the rule(s) to the cells.
        /// </summary>
        [PexMethod]
        public virtual void Evolve() {
            Contract.Ensures(Generation == Contract.OldValue(Generation) + 1);
            Contract.Ensures(Contract.ForAll(0, Grid.Length,
                                             i => Grid[i].State.CurrentState == Grid[i].State.NextState));
            // Calculate new states from current states
            for (uint index = 0; index < Grid.Length; index++)
                Grid[index].State.NextState = Grid[index].Rule.ApplyRule(GetCoordinate(index), this);

            // Commit new states
            foreach (Cell cell in Grid) cell.State.CurrentState = cell.State.NextState;
            Generation++;
        }

        /// <summary>
        ///   Indexer for the CA's grid of cells.
        /// </summary>
        /// <param name = "coord">coordinate</param>
        /// <returns>Cell at the given coordinate</returns>
        public virtual int this[params int[] coord] {
            get {
                Contract.Requires(coord.Length == Dimensions);
                return GetCell(coord).State.CurrentState;
            }
            set {
                Contract.Requires(coord.Length == Dimensions);
                Contract.Ensures(GetCell(coord).State.CurrentState.Equals(value));
                Grid[GetIndex(coord)].State.CurrentState = value;
            }
        }

        /// <summary>
        ///   For iterating over the entire grid.
        /// </summary>
        /// <returns></returns>
        [Pure]
        public IEnumerable<int> GridEnumerator() {
            return Grid.Select(cell => cell.State.CurrentState);
        }

        /// <summary>
        /// Returns a copy of the cellular automatons current grid of cells.
        /// </summary>
        /// <returns></returns>
        public Cell[] CopyOfGrid() {
            Contract.Ensures(Contract.ForAll(0, Grid.Length, i => Grid[i] == Contract.Result<Cell[]>()[i]));
            Cell[] copy = new Cell[Grid.Length];
            for (int i = 0; i < Grid.Length; i++) {
                Cell c = Grid[i];
                copy[i] = new Cell(c.Rule, c.State.SpaceMin, c.State.SpaceMax, c.State.CurrentState);
            }
            return copy;
        }

        [ContractInvariantMethod]
        // ReSharper disable UnusedMember.Local
        private void ObjInvar() {
            // ReSharper restore UnusedMember.Local
            Contract.Invariant(0 <= Generation);
            Contract.Invariant(0 < DimensionSize);
            Contract.Invariant(0 < Dimensions);
            Contract.Invariant(Grid.Length == (int)Math.Pow((double)DimensionSize, (double)Dimensions));
            Contract.Invariant(Contract.ForAll(Grid, c => null != c));
            Contract.Invariant(null != DefaultRule);
        }

        public override
            string ToString() {
            String s = "", t = "";
            foreach (int i in GridEnumerator()) {
                s += i;
                if (s.Length < 1 * DimensionSize) continue;
                t += s + "\n";
                s = "";
            }
            return t;
        }

        public object Clone() {
            return new CellularAutomaton(Dimensions, DimensionSize, DefaultRule) {
                Generation = Generation,
                Grid = CopyOfGrid()
            };
        }
    }

    /// <author>Anders Øland / anoe@itu.dk</author>
    /// <summary>
    /// A specialized Cellular Automaton for Wolfram one-dimensional automata.
    /// The grid does not have a torus shape (coordinates are not 'wrapper around'), instead the actual size of the grid is in fact
    /// larger than the specified size - so borderline cells can be processed.
    /// </summary>
    public class WolframAutomaton : CellularAutomaton {
        public uint ViewedSize { get; private set; }
        private readonly Cell[,] _viewedGrid;

        /// <summary>
        /// Create a new WolframAutomaton.
        /// </summary>
        /// <param name="wolframRule"></param>
        /// <param name="stateSpace">size of the state space, 2 = binary</param>
        /// <param name="viewedSize">size of the rectangular grid displaying the CA</param>
        /// <param name="actualSize">actual size of the CA in memmory</param>
        public WolframAutomaton(Rule wolframRule, int stateSpace, uint viewedSize = 64u, uint actualSize = 100u)
            : base(1, actualSize, wolframRule, 0, stateSpace - 1) {
            Contract.Requires(3 <= viewedSize);
            Contract.Requires(viewedSize <= actualSize);
            Contract.Requires(null != wolframRule);
            Contract.Requires(1 < stateSpace);
            ViewedSize = viewedSize;
            _viewedGrid = new Cell[ViewedSize, ViewedSize];
            base.GetCell((int)(actualSize / 2)).State.CurrentState = 1;
            UpdateViewedGrid();
            for (int i = 0; i < viewedSize; i++) Evolve();
        }

        /// <summary>
        ///   Returns a cell from the viewed two-dimensional grid.
        /// </summary>
        protected internal new Cell GetCell(params int[] coord) {
            Contract.Requires(coord.Length == 2);
            Contract.Ensures(Contract.Result<Cell>() != null);
            return _viewedGrid[coord[0], coord[1]];
        }

        public override sealed void Evolve() {
            base.Evolve();
            UpdateViewedGrid();
        }

        private void UpdateViewedGrid() {
            Contract.Requires(ViewedSize <= DimensionSize);
            Contract.Requires(null != _viewedGrid);
            Contract.Requires(null != Grid);
            for (int y = 0; y < ViewedSize; y++) {
                // Shift left
                for (int x = 0; x < ViewedSize - 1; x++) _viewedGrid[x, y] = _viewedGrid[x + 1, y];
                // Add new generation
                _viewedGrid[ViewedSize - 1, y] = base.GetCell((int)(ViewedSize / 2 - y + (int)DimensionSize / 2));
            }
        }
    }
}